package jrobutt.room.level;

import jrobutt.*;

import java.awt.*;
import java.util.*;
import java.util.List;

class OilDrop implements Renderable
{
    public static final Color oil_color = new Color(192, 161, 114);
    /* NOTE: THIS CAN NEVER BE < 1.0f */
    static final float default_combustion_duration = 1.0f;
    
    int grid_x;
    int grid_y;
    
    int size;
    
    boolean is_gonna_combust;
    float combustion_timer;
    
    boolean is_on_fire;
    
    private Color get_current_color ()
    {
        if (is_on_fire)
        {
            return Color.RED;
        }
        else if (is_gonna_combust)
        {
            return Color.YELLOW;
        }
        else
        {
            return oil_color;
        }
    }
    
    OilDrop (int grid_x, int grid_y, int size)
    {
        this.grid_x = grid_x;
        this.grid_y = grid_y;
        
        this.size = size;
        
        is_gonna_combust = false;
        combustion_timer = -1.0f;
        is_on_fire = false;
    }
    
    public void start_to_combust ()
    {
        start_to_combust(default_combustion_duration);
    }
    
    
    private void start_to_combust (float duration)
    {
        if (is_on_fire) return;
        
        if (is_gonna_combust)
        {
            combustion_timer = Math.min(combustion_timer, duration);
        }
        else
        {
            // event: actually_start_to_combust()
            combustion_timer = duration;
            is_gonna_combust = true;
        }
    }
    
    void decrement_timer ()
    {
        if (is_gonna_combust)
        {
            combustion_timer -= 1.0f;
        }
    }
    
    void handle_flammability_and_stuff (Collection<OilDrop> adjacent_drops)
    {
        if (!(is_gonna_combust || is_on_fire)) return;
        
        float timer_leftover = 0.0f;
        if (is_gonna_combust)
        {
            if (combustion_timer <= 0.0f)
            {
                // event: just_burst_into_flames() or whatever
                is_gonna_combust = false;
                is_on_fire = true;
                timer_leftover = -combustion_timer;
                combustion_timer = -1.0f;
            }
        }
        
        if (is_on_fire)
        {
            for (OilDrop adjacent_drop : adjacent_drops)
            {
                float dist = GameMath.point_distance(grid_x, grid_y, adjacent_drop.grid_x, adjacent_drop.grid_y);
                float adjacent_drop_duration = default_combustion_duration * dist;
                if (timer_leftover > 0.0f)
                {
                    // TWO HUGE ASSUMPTIONS:
                    // (1) timer_leftover is never >= 1.0f
                    //     because combustion_timer only decrements by 1.0f every step
                    // (2) adjacent_drop_duration is never < 1.0f
                    //     because adjacent_oil_drop is always at least 1.0f away and
                    //     default_combustion_duration is at least 1.0f
                    adjacent_drop_duration -= timer_leftover;
                }
                adjacent_drop.start_to_combust(adjacent_drop_duration);
            }
        }
    }
    
    public void render (Graphics2D g)
    {
        g.setColor(get_current_color());
        g.fillRect(grid_x*size, grid_y*size, size, size);
    }
}

public class OilGrid extends MultiRenderable implements Updatable
{
    public int drop_size;
    public Guy subject;
    private Map<Point, OilDrop> oil_drops;
    
    private Point first_cell;
    private float initial_timer;
    
    private float prev_sx;
    private float prev_sy;
    
    protected Collection<? extends Renderable> get_children ()
    {
        return oil_drops.values();
    }
    
    public OilGrid (int drop_size, Guy subject)
    {
        this.drop_size = drop_size;
        this.subject = subject;
        
        first_cell = get_grid_point_at(subject.oil_hole.x, subject.oil_hole.y);
        prev_sx = subject.oil_hole.x;
        prev_sy = subject.oil_hole.y;
        
        oil_drops = new HashMap<Point, OilDrop>();
        
        initial_timer = 30.0f * Settings.fps;
    }
    
    private Point get_grid_point_at (float x, float y)
    {
        return new Point((int)Math.floor(x / drop_size), (int)Math.floor(y / drop_size));
    }
    private Collection<Point> get_grid_points_between (float x1, float y1, float x2, float y2)
    {
        List<Point> retval = new LinkedList<Point>();
        
        float xmin = Math.min(x1, x2);
        float ymin = Math.min(y1, y2);
        float xmax = Math.max(x1, x2);
        float ymax = Math.max(y1, y2);
        int xxmin = (int)Math.floor(xmin / drop_size);
        int yymin = (int)Math.floor(ymin / drop_size);
        int xxmax = (int)Math.floor(xmax / drop_size) + 1;
        int yymax = (int)Math.floor(ymax / drop_size) + 1;
        for (int k=xxmin; k<xxmax; k++) for (int i=yymin; i<yymax; i++)
        {
            float rect_x = k * drop_size;
            float rect_y = i * drop_size;
            if (CollisionMaster.are_overlapping(x1, y1, x2, y2, rect_x, rect_y, drop_size, drop_size))
            {
                retval.add(new Point(k, i));
            }
        }
        
        return retval;
    }
    
    private Collection<Point> adjacent_cells (Point cell)
    {
        List<Point> adjacent_cells = new ArrayList<Point>(8);
        
        for (int dx=-1; dx<=1; dx++) for (int dy=-1; dy<=1; dy++)
        {
            int x = cell.x + dx;
            int y = cell.y + dy;
            if (!(cell.x == x && cell.y == y))
            {
                adjacent_cells.add(new Point(x, y));
            }
        }
        
        return adjacent_cells;
    }
    
    private void add_oil_drop (Point p)
    {
        if (!oil_drops.containsKey(p))
        {
            OilDrop oil_drop = new OilDrop(p.x, p.y, drop_size);
            oil_drops.put(p, oil_drop);
        }
    }
    
    private void add_oil_drop_at (float x, float y)
    {
        add_oil_drop(get_grid_point_at(x, y));
    }
    
    public void update ()
    {
        if (initial_timer > 0.0f)
        {
            initial_timer -= 1.0f;
            if (initial_timer <= 0.0f)
            {
                // event: first_drop_gets_combusted
                OilDrop first_oil_drop = oil_drops.get(first_cell);
                if (first_oil_drop != null)
                {
                    first_oil_drop.start_to_combust();
                }
                else
                {
                    // TODO -- log error? this should never happen
                }
            }
        }
        
        float sx = subject.oil_hole.x;
        float sy = subject.oil_hole.y;
        for (Point p : get_grid_points_between(prev_sx, prev_sy, sx, sy))
        {
            add_oil_drop(p);
        }
        prev_sx = sx;
        prev_sy = sy;
        
        for (OilDrop oil_drop : oil_drops.values())
        {
            oil_drop.decrement_timer();
        }
        
        for (Map.Entry<Point, OilDrop> pair : oil_drops.entrySet())
        {
            Point oil_grid_point = pair.getKey();
            OilDrop oil_drop = pair.getValue();
            
            List<OilDrop> adjacent_oil_drops = new LinkedList<OilDrop>();
            for (Point p : adjacent_cells(oil_grid_point))
            {
                OilDrop adjacent_oil_drop = oil_drops.get(p);
                if (adjacent_oil_drop != null)
                {
                    adjacent_oil_drops.add(adjacent_oil_drop);
                }
            }
            
            oil_drop.handle_flammability_and_stuff(adjacent_oil_drops);
        }
        
        if (!subject.is_dead)
        {
            sx = subject.x;
            sy = subject.y;
            float sr = subject.radius;
            int xxmin = (int)Math.floor((sx - sr) / drop_size);
            int yymin = (int)Math.floor((sy - sr) / drop_size);
            int xxmax = (int)Math.ceil((sx + sr) / drop_size) - 1;
            int yymax = (int)Math.ceil((sy + sr) / drop_size) - 1;
            for (int k=xxmin; k<=xxmax; k++) for (int i=yymin; i<=yymax; i++)
            {
                OilDrop oil_drop = oil_drops.get(new Point(k, i));
                if (oil_drop == null) continue;
                
                float rect_x = k * drop_size;
                float rect_y = i * drop_size;
                float poi_x = Math.min(Math.max(rect_x, sx), rect_x + drop_size);
                float poi_y = Math.min(Math.max(rect_y, sy), rect_y + drop_size);
                if (!(GameMath.point_distance_sqr(sx, sy, poi_x, poi_y) < GameMath.sqr(subject.radius))) continue;
                
                if (oil_drop.is_on_fire)
                {
                    subject.kill();
                    break;
                }
            }
        }
    }
    
    public void remove_oil_within (float x, float y, float width, float height)
    {
        int xxmin = (int)Math.floor((x) / drop_size);
        int yymin = (int)Math.floor((y) / drop_size);
        int xxmax = (int)Math.ceil((x + width) / drop_size) - 1;
        int yymax = (int)Math.ceil((y + height) / drop_size) - 1;
        for (int k=xxmin; k<=xxmax; k++) for (int i=yymin; i<=yymax; i++)
        {
            Point p = new Point(k, i);
            oil_drops.remove(p);
        }
    }
}
